Skip to content

feat: in-container Tailscale SSH support (#85)#548

Open
gerchowl wants to merge 1 commit into
mainfrom
feature/85-tailscale-in-container
Open

feat: in-container Tailscale SSH support (#85)#548
gerchowl wants to merge 1 commit into
mainfrom
feature/85-tailscale-in-container

Conversation

@gerchowl

Copy link
Copy Markdown
Contributor

Summary

Bakes Tailscale into the image and ships the workspace template with the device + caps + state volume needed for the daemon to run with real TUN networking. Opt-in via `TAILSCALE_AUTHKEY`; no-op without it.

Closes #85

Architecture

Layer What changes
Image (Containerfile) Static `tailscale` + `tailscaled` binaries baked from `pkgs.tailscale.com/stable/`, sha256-verified. ~+25MB
Compose template `/dev/net/tun` + `NET_ADMIN`/`NET_RAW` + `tailscale-state` volume shipped by default. Idle until `TAILSCALE_AUTHKEY` is set.
`setup-tailscale.sh` Single `connect` subcommand. Idempotent (`tailscale status --self` check). Fail-loud on missing TUN. Hostname auto-derived from `devcontainer.json` `name`, sanitized to valid DNS label.
`post-start.sh` Calls `setup-tailscale.sh connect` (failure non-fatal — container still usable via devcontainer protocol)

Why these architectural choices

  • Real TUN, not userspace networking: Tailscale SSH server requires a real TUN device. `--tun=userspace-networking` works for outbound but cannot serve inbound SSH, which is the entire point of this feature.
  • Bake binary, drop install: simpler than the apt clock-skew dance + signed-keyring + repo-file at start time. Mirrors the pattern used for `gh`/`just`/`taplo`/`uv`/`hadolint`.
  • State volume: previous design wiped state on every recreate, causing ephemeral-key collisions on the tailnet (old node still online, hostname conflict). Persistent volume = same node identity reused.
  • Fail-loud on missing TUN: previous design quietly fell back to userspace-networking with a single stderr WARNING line buried in compose output → users never saw it, then wondered why SSH didn't work.
  • Idempotent: `tailscale status --self` check before `tailscale up` avoids regenerating sessions on every container start.

What's deferred (separate scope)

  • `just tailscale up/down/status/restart` recipes for manual operator invocation. Tracked separately so this PR stays focused on the IN-container plumbing.
  • Programmatic OAuth-API auth-key generation from the host side. Belongs in `devc-remote.sh`'s scope.

Validation

Test plan

  • CI green (build + tests)
  • x86_64 build + tests on ksb-meatgrinder (real Linux kernel; user will retest)
  • Live integration on ksb-meatgrinder: deploy with a real OAuth-generated ephemeral key, verify the container's hostname appears in `tailscale status` from the Mac, verify `ssh root@` from Mac into container works.

Related

Part of the scope-split rework of #166 — see also #547 (image toolkit) and #546 (slim Claude Code OAuth-token forwarding).

@gerchowl gerchowl requested a review from c-vigo as a code owner May 15, 2026 14:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Add Tailscale support for remote SSH access to devcontainer

1 participant